package com.yuanrui.subject.infra.basic.service.impl;

import com.yuanrui.subject.common.entity.PageResult;
import com.yuanrui.subject.common.enums.SubjectInfoTypeEnum;
import com.yuanrui.subject.infra.basic.entity.EsSubjectFields;
import com.yuanrui.subject.infra.basic.entity.SubjectInfoEs;
import com.yuanrui.subject.infra.basic.es.EsIndexInfo;
import com.yuanrui.subject.infra.basic.es.EsRestClient;
import com.yuanrui.subject.infra.basic.es.EsSearchRequest;
import com.yuanrui.subject.infra.basic.es.EsSourceData;
import com.yuanrui.subject.infra.basic.service.SubjectEsService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.common.text.Text;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.MatchQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;

@Service
@Slf4j
public class SubjectEsServiceImpl implements SubjectEsService {


    @Override
    public boolean insert(SubjectInfoEs subjectInfoEs) {
        EsSourceData esSourceData = new EsSourceData();
        Map<String, Object> data = convertToEsSourceData(subjectInfoEs);
        esSourceData.setDocId(subjectInfoEs.getDocId().toString());
        esSourceData.setData(data);
        return EsRestClient.insertDoc(getEsIndexInfo(), esSourceData);
    }

    private Map<String, Object> convertToEsSourceData(SubjectInfoEs subjectInfoEs) {
        Map<String, Object> data = new HashMap<>();
        data.put(EsSubjectFields.SUBJECT_ID, subjectInfoEs.getSubjectId());
        data.put(EsSubjectFields.DOC_ID, subjectInfoEs.getDocId());
        data.put(EsSubjectFields.SUBJECT_NAME, subjectInfoEs.getSubjectName());
        data.put(EsSubjectFields.SUBJECT_ANSWER, subjectInfoEs.getSubjectAnswer());
        data.put(EsSubjectFields.CREATED_USER, subjectInfoEs.getCreatedUser());
        data.put(EsSubjectFields.CREATED_TIME, subjectInfoEs.getCreatedTime());
        data.put(EsSubjectFields.SUBJECT_TYPE, subjectInfoEs.getSubjectType());
        data.put(EsSubjectFields.KEYWORD, subjectInfoEs.getKeyword());
        data.put(EsSubjectFields.SCORE, subjectInfoEs.getScore());
        return data;
    }

    @Override
    public PageResult<SubjectInfoEs> querySubjectList(SubjectInfoEs req) {
        String keyword = req.getKeyword();
        if (keyword == null || keyword.isEmpty()) {
             return new PageResult<>();
        }
        PageResult<SubjectInfoEs> pageResult = new PageResult<>();
        EsSearchRequest esSearchRequest = creatSearchListQuery(req);
        SearchResponse searchResponse = EsRestClient.searchWithTermQuery(esSearchRequest, getEsIndexInfo());
        if (searchResponse == null) {
            return pageResult;
        }
        List<SubjectInfoEs> subjectInfoEs = new LinkedList<>();
        SearchHits hits = searchResponse.getHits();
        if (hits == null || hits.getTotalHits() == null) {
            pageResult.setPageNo(req.getPageNo());
            pageResult.setPageSize(req.getPageSize());
            pageResult.setTotal(0);
            pageResult.setResult(subjectInfoEs);
            return pageResult;
        }

        //匹配到了数据
        SearchHit[] hitsResult = hits.getHits();
        for (SearchHit document : hitsResult) {
            SubjectInfoEs subjectInfo = convertToSubjectInfoEs(document);
            if (Objects.nonNull(subjectInfo)) {
                subjectInfoEs.add(subjectInfo);
            }
        }
        pageResult.setPageNo(req.getPageNo());
        pageResult.setPageSize(req.getPageSize());
        pageResult.setTotal((int) hits.getTotalHits().value);
        pageResult.setResult(subjectInfoEs);
        return pageResult;
    }

    /**
     * 将es查询结果转换为题目信息
     *
     * @param document es查询结果
     * @return 题目信息
     */
    private SubjectInfoEs convertToSubjectInfoEs(SearchHit document) {
        Map<String, Object> sourceAsMap = document.getSourceAsMap();
        if (sourceAsMap == null || sourceAsMap.isEmpty()) {
            return null;
        }
        SubjectInfoEs subjectInfoEs = new SubjectInfoEs();
        subjectInfoEs.setSubjectId(MapUtils.getLong(sourceAsMap, EsSubjectFields.SUBJECT_ID));
        subjectInfoEs.setDocId(MapUtils.getLong(sourceAsMap, EsSubjectFields.DOC_ID));
        subjectInfoEs.setSubjectName(MapUtils.getString(sourceAsMap, EsSubjectFields.SUBJECT_NAME));
        subjectInfoEs.setSubjectAnswer(MapUtils.getString(sourceAsMap, EsSubjectFields.SUBJECT_ANSWER));
        subjectInfoEs.setCreatedUser(MapUtils.getString(sourceAsMap, EsSubjectFields.CREATED_USER));
        subjectInfoEs.setCreatedTime(MapUtils.getLong(sourceAsMap, EsSubjectFields.CREATED_TIME));
        subjectInfoEs.setSubjectType(MapUtils.getInteger(sourceAsMap, EsSubjectFields.SUBJECT_TYPE));
        subjectInfoEs.setKeyword(MapUtils.getString(sourceAsMap, EsSubjectFields.KEYWORD));
        subjectInfoEs.setScore(BigDecimal.valueOf(document.getScore()).multiply(BigDecimal.valueOf(100)).setScale(2, RoundingMode.HALF_UP));
        //处理Name高亮字段
        Map<String, HighlightField> highlightFields = document.getHighlightFields();
        HighlightField subjectNameHighlight = highlightFields.get(EsSubjectFields.SUBJECT_NAME);
        if (Objects.nonNull(subjectNameHighlight)) {
            //fragment是高亮的片段
            Text[] fragments = subjectNameHighlight.getFragments();
            StringBuilder subjectNameBuilder = new StringBuilder();
            for (Text fragment : fragments) {
                subjectNameBuilder.append(fragment.string());
            }
            //将高亮的片段替换原来的字段，返回给前端
            subjectInfoEs.setSubjectName(subjectNameBuilder.toString());
        }
        //处理答案的高亮
        HighlightField subjectAnswerHighlight = highlightFields.get(EsSubjectFields.SUBJECT_ANSWER);
        if (Objects.nonNull(subjectAnswerHighlight)) {
            Text[] fragments = subjectAnswerHighlight.getFragments();
            StringBuilder subjectAnswerBuilder = new StringBuilder();
            for (Text fragment : fragments) {
                subjectAnswerBuilder.append(fragment.string());
            }
            subjectInfoEs.setSubjectAnswer(subjectAnswerBuilder.toString());
        }
        return subjectInfoEs;
    }

    /**
     * 创建查询题目列表的查询条件
     *
     * @param req 查询条件
     * @return 查询条件
     */
    private EsSearchRequest creatSearchListQuery(SubjectInfoEs req) {
        EsSearchRequest esSearchRequest = new EsSearchRequest();
        BoolQueryBuilder bq = new BoolQueryBuilder();
        MatchQueryBuilder subjectNameQueryBuilder =
                QueryBuilders.matchQuery(EsSubjectFields.SUBJECT_NAME, req.getKeyword());
        bq.should(subjectNameQueryBuilder);
        //提高题目名称的权重
        subjectNameQueryBuilder.boost(2);
        MatchQueryBuilder subjectAnswerQueryBuilder =
                QueryBuilders.matchQuery(EsSubjectFields.SUBJECT_ANSWER, req.getKeyword());
        bq.should(subjectAnswerQueryBuilder);

        //简答题
        MatchQueryBuilder subjectBriefQueryBuilder =
                QueryBuilders.matchQuery(EsSubjectFields.SUBJECT_TYPE, SubjectInfoTypeEnum.BRIEF.getCode());
        bq.must(subjectBriefQueryBuilder);
        bq.minimumShouldMatch(1);

        //增加高亮
        //requireFieldMatch(false)表示如果没有匹配到字段，也会返回高亮
        //preTags和postTags表示高亮的前后缀
        HighlightBuilder highlightBuilder = new HighlightBuilder()
                .field("*").requireFieldMatch(false);
        highlightBuilder.preTags("<span style='color:red'>");
        highlightBuilder.postTags("</span>");
        esSearchRequest.setBq(bq);
        esSearchRequest.setHighlightBuilder(highlightBuilder);
        esSearchRequest.setFields(EsSubjectFields.FIELDS);
        esSearchRequest.setFrom((req.getPageNo() - 1) * req.getPageSize());
        esSearchRequest.setSize(req.getPageSize());
        esSearchRequest.setNeedScroll(false);
        return esSearchRequest;
    }

    private EsIndexInfo getEsIndexInfo() {
        EsIndexInfo esIndexInfo = new EsIndexInfo();
        esIndexInfo.setClusterName("c465ab5d8fa3");
        esIndexInfo.setIndexName("subject_index");
        return esIndexInfo;
    }
}
